package org.kca.graphics;

import org.kca.yarmour.utils.KcaGLUtils;

/**
 * This class is the implementation of an animation using an animation queue.
 * Load the animation frames one by one using addFrame method. Use makeCircular to 
 * make the animation repetitive. This class allows only static animations (i.e, the 
 * top left of the image will always be 0,0).
 * @author Celestine Lau
 *
 */
public class Animation implements Displayable{
	
	/** The head of the linked list */
	private AnimFrame head;
	/** The current frame in the animation */
	private AnimFrame current;
	/** This keeps track of whether the animation is currently running */
	private boolean isRunning;
	/** This keeps track of the current time left in the current frame */
	private int currentDelay;
	/** Keeps track of whether the animation is a looping one */
	private boolean isCircular;
	
	/**
	 * Constructor for a new animation. 
	 */
	public Animation (){
		head = null;
		current = null;
		isRunning = false;
		currentDelay = 0;
		isCircular = false;
	}
	
	/**
	 * Adds an animation frame to the animation. Make sure you add all animations before
	 * you call makeCircular or start the animation. Also, it is strongly recommended that
	 * all images be of the same size.
	 * @param image The image of the current frame
	 * @param duration The duration of this animation frame in milliseconds
	 * @throws UnsupportedOperationException if this method is called after the animation has
	 * started or after this animation is made circular
	 * @throws IllegalArgumentException if duration is not positive or image is null.
	 */
	public void addAnimation(Texture image, int duration){
		if (isRunning || isCircular) 
			throw new UnsupportedOperationException("May not add any more animations.");
		if (image == null || duration <= 0)
			throw new IllegalArgumentException("Illegal arguments on addAnimation call");
		AnimFrame newAnim = new AnimFrame(image, duration);
		if (head == null){
			head = newAnim;
		}else {
			AnimFrame temp = head;
			while (temp.nextAnim != null) temp = temp.nextAnim;
			temp.nextAnim = newAnim;
		}
	}
	
	/**
	 * Makes the animation looping. Does nothing if it is already circular. May not be called
	 * if the animation is currently running
	 * @throws UnsupportedOperationException if this method is called after the animation starts.
	 */
	public void makeCircular(){
		if (isRunning) 
			throw new UnsupportedOperationException("May not make circular after animation has started.");
		if (head == null) throw new NullPointerException();
		if (isCircular) return;
		else isCircular = true;
		AnimFrame temp = head;
		while (temp.nextAnim != null) temp = temp.nextAnim;
		temp.nextAnim = head;
	}
	
	/**
	 * Resets the animation to the beginning. Also stops the animation.
	 * @throws NullPointerException if no animation frames have been added.
	 */
	public void reset(){
		if (head == null) throw new NullPointerException("Attempt to reset null animation.");
		current = head;
		currentDelay = current.getDelay();
		isRunning = false;
	}
	
	/**
	 * Starts the animation from the beginning. Does nothing if animation has already
	 * started.
	 * @throws NullPointerException if no animation frames have been added.
	 */
	public void start(){
		reset();
		isRunning = true;
	}
	
	/**
	 * Pauses the animation.
	 */
	public void pause(){
		isRunning = !isRunning;
	}
	
	public boolean isAnimationRunning(){
		return isRunning;
	}
	
	// see interface
	public void drawComponent(int x, int y)
	{
		KcaGLUtils.drawImage(current.getImage(), x, y);
	}
	
	// see interface
	public void update(long elapsedTime){
		if (!isRunning) return;
		currentDelay -= elapsedTime;
		while (currentDelay < 0){
			current = current.nextAnim;
			if (current == null){
				currentDelay = 0;
				isRunning = false;
				return;
			}
			currentDelay += current.getDelay();
		}
	}
	
	public Animation copy(){
		Animation a = new Animation();
		AnimFrame temp = head;
		while (temp != null){
			a.addAnimation(temp.getImage(), temp.getDelay());
			temp = temp.nextAnim;
		}
		return a;
	}
	
	public String toString(){
		String s = "";
		AnimFrame temp = head;
		while (temp != null){
			s += temp.getDelay() + " ";
			temp = temp.nextAnim;
		}
		return s;
	}
	
	
	/**
	 * A node in a linked list of animations.
	 */
	private static class AnimFrame{
		
		/** Stores the image of the current frame */
		private Texture image;
		/** The duration of the current frame */
		private int delay;
		/** Pointer to the next animation */
		public AnimFrame nextAnim;
		
		/**
		 * Constructor for the animation frame. 
		 * @param img The image of the current frame
		 * @param delay The duration of the current frame
		 */
		public AnimFrame(Texture img, int delay){
			this.image = img;
			this.delay = delay;
			nextAnim = null;
		}
		
		/**
		 * Gets the image of the current frame
		 * @return the image of the current frame
		 */
		public Texture getImage(){
			return image;
		}
		
		/**
		 * Gets the duration of the current frame
		 * @return The duration of the current frame
		 */
		public int getDelay(){
			return delay;
		}
		
	}
	
}
